package com.example.OnlineStorage.service.FileOperate;

import com.example.OnlineStorage.Dao.FileSHA256Dao;
import com.example.OnlineStorage.Dao.UserDao;
import com.example.OnlineStorage.enums.GlobalVariables_String;
import com.example.OnlineStorage.exception.CustomException;
import com.example.OnlineStorage.pojo.entity.FileSha256;
import com.example.OnlineStorage.pojo.entity.Node;
import com.example.OnlineStorage.pojo.entity.User;
import com.example.OnlineStorage.util.IO;
import com.example.OnlineStorage.util.Redis;
import com.example.OnlineStorage.util.SHA256;
import com.example.OnlineStorage.util.Verify;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.math.BigInteger;
import java.util.*;

@Service
public class IFileOperateService implements FileOperateService {
	
	private final UserDao userDao;
	
	private final FileSHA256Dao fileSha256Dao;
	
	public IFileOperateService (UserDao userDao,
	                            FileSHA256Dao fileSha256Dao
	) {
		this.userDao = userDao;
		this.fileSha256Dao = fileSha256Dao;
	}
	
	/**
	 * 更新用户信息并刷新会话。
	 *
	 * @param user 用户信息。
	 * @param UUID 当前会话ID。
	 * @return 更新后的用户信息。
	 * @throws RuntimeException 如果更新失败。
	 */
	private User getUser (User user, String UUID) {
		// 更新用户信息并验证更新是否成功
		try {
			if (this.userDao.update(user) > 0) {
				Redis.HSET(user.getUsername(), "user", user.toJson());
				Redis.EXPIRE(UUID, 60 * 30);
				Redis.EXPIRE(user.getUsername(), 60 * 30);
				return user;
			}
			throw new RuntimeException("信息更新失败");
		} catch (Exception e) {
			throw new RuntimeException("服务器异常");
		}
	}
	
	private Map<String, Object> getStringObjectMap (String name, String intactSHA256, String UUID, User user, Node root, Node node, Map<String, Object> map, String type, String[] dir, BigInteger size) {
		
		try {
			String fileDataJson;
			node = node.createDirectoryArray(dir);
			node.createChild(name, intactSHA256, false, type, size);
            
            Long count = fileSha256Dao.addCount(intactSHA256);
            if (count != 1) {
                throw new CustomException(500,"文件上传成功,写入数据库失败,可以尝试第二次请求");
            }
			user.setFileDataJson(root.toJson());
			user.setSize(root.getNodeSize());
			fileDataJson = getUser(user, UUID).getFileDataJson();
			
			map.put("isExist", true);
			map.put("data", fileDataJson);
			return map;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	private Map<String, Object> getUserAndNode (String fromPath, String UUID) {
		Map<String, Object> map = new HashMap<>();
		User user = User.fromJson(Redis.HGET(Redis.GET(UUID), "user"));
		String fileDataJson = user.getFileDataJson();
		Node root = Node.fromJson(fileDataJson);
		Node node = root.getChildByPath(fromPath);
		map.put("user", user);
		map.put("root", root);
		map.put("node", node);
		return map;
	}

    @SneakyThrows
	@Override
	public String getFileDataJson(String UUID){
		try{
			User user = User.fromJson(Redis.HGET(Redis.GET(UUID), "user"));
			String fileDataJson = user.getFileDataJson();
			Node root = Node.fromJson(fileDataJson);
			return root.toJson();
		} catch (Exception e){
			throw new CustomException(400, e.getMessage());
		}
	}


	@SneakyThrows
	@Override
	public String createDirectory (String fromPath, String name, String UUID) {
		try {
			Map<String, Object> UserAndNode = getUserAndNode(fromPath, UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			Node node = (Node) UserAndNode.get("node");
			if (node == null||node.isFile()) {
				throw new RuntimeException("路径选择错误");
			}
			if (!name.matches("^[a-zA-Z0-9\\u4e00-\\u9fa5]+$")) {
				throw new RuntimeException( "名称不能包含特殊字符");
			}
			node.createChild(name, "", true, "directory", null);
			user.setFileDataJson(root.toJson());
			return getUser(user, UUID).getFileDataJson();
		} catch (Exception e) {
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
	}
	
	@SneakyThrows
	@Override
	public Map<String, Object> isExist (String fromPath, String name, String intactSHA256, List<String> AllChunkSHA256, String UUID) {
		try {
			Map<String, Object> UserAndNode = getUserAndNode(fromPath, UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			Node node = (Node) UserAndNode.get("node");
			if (node == null||node.isFile()) {
				throw new RuntimeException("路径选择错误");
			}
			Map<String, Object> map = new HashMap<>();
			if (Verify.VerifyFileIsExistCourseResource(intactSHA256)) {
				String[] Dir = name.split("/");
				name = Dir[Dir.length - 1];
				String type = IO.getFileType(GlobalVariables_String.File_Path_Files.getValue() + intactSHA256);
				BigInteger fileSize = IO.getFileSize(GlobalVariables_String.File_Path_Files.getValue() + intactSHA256);
				return getStringObjectMap(name, intactSHA256, UUID, user, root, node, map, type, Dir, fileSize);
			}
			for (String SHA_256 : AllChunkSHA256) {
				if (! SHA_256.equals(Redis.HGET(intactSHA256, SHA_256))){
					map.put("isExist", false);
					map.put("ChunkSHA256", SHA_256);
					break;
				}
			}
			return map;
		} catch (Exception e) {
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
	}
	
	/**
	 * 将文件块上传到服务器并合并成完整文件。
	 * 此方法处理文件的分块上传和完整性验证，确保文件按预期上传和存储。
	 *
	 * @param fromPath       上传文件的路径。
	 * @param name           文件名如果用户。
	 * @param intactSHA256   完整文件的SHA256校验和。
	 * @param ChunkSHA256    当前上传块的SHA256校验和。
	 * @param AllChunkSHA256 所有块的SHA256校验和数组。
	 * @param ChunkFile      当前上传的文件块。
	 * @param UUID           用户唯一标识符，用于检索用户信息和文件元数据。
	 * @return 包含上传状态和可能的文件元数据的Map。
	 */
	@SneakyThrows
	@Override
	public Map<String, Object> putChunk (String fromPath,
	                                     String name,
	                                     String intactSHA256,
	                                     String ChunkSHA256,
	                                     List<String> AllChunkSHA256,
	                                     MultipartFile ChunkFile,
	                                     String UUID) {
		// 构建临时文件路径
		String tempPath = GlobalVariables_String.File_Path_Temp.getValue() + intactSHA256;
		try {
			// 从Redis中获取用户信息
			Map<String, Object> UserAndNode = getUserAndNode(fromPath, UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			Node node = (Node) UserAndNode.get("node");
			
			// 检查路径是否有效
			if (node == null|| node.isFile()) {
				throw new RuntimeException("路径选择错误");
			}
			
			// 检查文件是否已存在
			boolean isExist = Verify.VerifyFileIsExistCourseResource(intactSHA256);
			Map<String, Object> map = new HashMap<>();
			if (isExist) {
				// 处理文件已存在的情况
				String[] Dir = name.split("/");
				name = Dir[Dir.length - 1];
				String type = IO.getFileType(GlobalVariables_String.File_Path_Files.getValue() + intactSHA256);
				BigInteger fileSize = IO.getFileSize(GlobalVariables_String.File_Path_Files.getValue() + intactSHA256);
				return getStringObjectMap(name, intactSHA256, UUID, user, root, node, map, type, Dir, fileSize);
			}
			
			// 验证块是否存在
			if (Redis.HEXISTS(intactSHA256, ChunkSHA256)) {
				for (String SHA_256 : AllChunkSHA256) {
					if (! Redis.HEXISTS(intactSHA256, SHA_256)) {
						map.put("isExist", false);
						map.put("ChunkSHA256", SHA_256);
						return map;
					}
				}
			}
			
			// 验证上传的块的SHA256
			String SHA_256 = SHA256.calculateFileSHA256(ChunkFile);
			if (! Objects.equals(SHA_256, ChunkSHA256)) {
				throw new RuntimeException("ChunkSHA256错误");
			}

			System.out.println(Redis.HLEN(intactSHA256));
			if (Redis.HLEN(intactSHA256) == 0) {
				IO.createFolder(tempPath);
			}

			// 写入块到临时文件夹
			IO.writeFile(ChunkFile, tempPath + "/" + SHA_256);

			// 记录块到Redis
			Redis.HSET(intactSHA256, ChunkSHA256, ChunkSHA256);

			// 检查是否所有块都已上传
			if (Redis.HLEN(intactSHA256) == AllChunkSHA256.size()) {
				String intactSHA256_ = SHA256.calculateIncrementalSHA256(AllChunkSHA256);
				if (! Objects.equals(intactSHA256, intactSHA256_)) {
					throw new RuntimeException("intactSHA256错误");
				}
				
				// 合并文件块并删除临时文件夹
				String writePath = GlobalVariables_String.File_Path_Files.getValue() + intactSHA256;
				String type = IO.mergeFile(tempPath + "/", AllChunkSHA256, writePath);
				BigInteger fileSize = IO.getFileSize(writePath);
				IO.deleteTempFolder(tempPath);
				
				// 更新文件节点和用户文件列表
				String[] Dir = name.split("/");
				name = Dir[Dir.length - 1];
                Integer count = fileSha256Dao.insert(new FileSha256(intactSHA256,fileSize));
                if (count != 1) {
                    throw new CustomException(500,"文件上传成功,写入数据库失败,可以尝试第二次请求");
                }
				return getStringObjectMap(name, intactSHA256, UUID, user, root, node, map, type, Dir, fileSize);
			}
			
			// 如果不是所有块都已上传，则检查缺失的块
			for (String sha256 : AllChunkSHA256) {
				if (! Redis.HEXISTS(intactSHA256, sha256)) {
					map.put("isExist", false);
					map.put("ChunkSHA256", sha256);
				}
			}
			return map;
		} catch (Exception e) {
			// 处理上传过程中的异常情况
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			if (e.getMessage().equals("文件块上传失败")) {
				Redis.HDEL(intactSHA256, ChunkSHA256);
			}
			if (e.getMessage().startsWith("intactSHA256")) {
				IO.deleteTempFolder(tempPath);
			}
			throw new CustomException(400, e.getMessage());
		}
	}

	/**
	 * 修改节点名称
	 *
	 * @param fromPath 要修改的路径
	 * @param newName  修改之后的名称
	 * @param UUID     Token
	 * @return 用户文件信息
	 */
	@SneakyThrows
	@Override
	public String reviseName (String fromPath,
	                          String newName,
	                          String UUID) {
		try {
			Map<String, Object> UserAndNode = getUserAndNode(fromPath, UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			Node node = (Node) UserAndNode.get("node");
			if (node == null) {
				throw new RuntimeException("路径错误");
			}
			if (!newName.matches("^[a-zA-Z0-9\\u4e00-\\u9fa5]+$")) {
				throw new RuntimeException( "名称不能包含特殊字符");
			}
			Node parentNode = root.getChildByPath(node.getParentPath());
			if(parentNode.getChildren().containsKey(newName)){
				throw new RuntimeException("该目录下已存在同名文件");
			}
			node.reviseName(newName,parentNode);
			user.setFileDataJson(root.toJson());
			return getUser(user, UUID).getFileDataJson();
		} catch (Exception e) {
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
	}

	/**
	 * 删除节点
	 *
	 * @param fromPath 要删除的路径
	 * @param UUID     Token
	 * @return 用户文件信息
	 */
	@SneakyThrows
	@Override
	public String deleteNode (String fromPath, String UUID) {
		try{
			Map<String, Object> UserAndNode = getUserAndNode(fromPath, UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			Node node = (Node) UserAndNode.get("node");
			if (node == null) {
				throw new RuntimeException("路径选择错误");
			}
			Node cacheNode =  Node.fromJson(node.toJson());
			List<String> AllSha256 = new ArrayList<>();
			if(root.removeChildByPath(fromPath)){
				AllSha256 = cacheNode.getAllSha256();
				String uuid = java.util.UUID.randomUUID() +"/";
				Redis.HSET("cache"+user.getUsername(),uuid+cacheNode.getName(),cacheNode.toJson());
				Redis.EXPIRE("cache"+user.getUsername(),60 * 60 * 24 * 10);
			}
			if(!AllSha256.isEmpty()){
				fileSha256Dao.subCounts(AllSha256);
			}
			user.setFileDataJson(root.toJson());
			return getUser(user, UUID).getFileDataJson();
		} catch (Exception e) {
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
	}

	/**
	 * 批量删除节点
	 *
	 * @param fromPath 要删除的路径数组
	 * @param UUID     Token
	 * @return 用户文件信息
	 */
	@SneakyThrows
	@Override
	public Map<String, Object> deleteNodes (String[]  fromPath, String UUID) {
		try{

			
			List<String> RemoveSha256 = new ArrayList<>();
			List<String> RemovePath = new ArrayList<>();
			List<String> FailPath = new ArrayList<>();
			Map<String, Object> UserAndNode = getUserAndNode("/", UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			for (String path : fromPath) {
				Node node = root.getChildByPath(path);
				if (node == null) {
					FailPath.add(path);
					continue;
				}
				Node cacheNode = Node.fromJson(node.toJson());
				if(root.removeChildByPath(path)){
					RemoveSha256.addAll(cacheNode.getAllSha256());
					RemovePath.add(path);
					String uuid = java.util.UUID.randomUUID() + "/";
					Redis.HSET("cache" + user.getUsername(), uuid + cacheNode.getName(), cacheNode.toJson());
					Redis.EXPIRE("cache" + user.getUsername(), 60 * 60 * 24 * 10);
				}
			}
			if(!RemoveSha256.isEmpty()){
				fileSha256Dao.subCounts(RemoveSha256);
			}
			user.setFileDataJson(root.toJson());
			Map<String, Object> map = new HashMap<>();
			map.put("RemovePath", RemovePath);
			map.put("FailPath", FailPath);
			map.put("data", getUser(user, UUID).getFileDataJson());
			return map;
   
		} catch (Exception e) {
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
}

	/**
	 * 恢复节点
	 *
	 * @param fromPath  要恢复到哪个路径
	 * @param cacheName 要恢复的缓存名称
	 * @param UUID      Token
	 * @return 用户文件信息
	 */
	@SneakyThrows
	@Override
	public String recoveryNode (String fromPath, String cacheName, String UUID) {
		try{
			Map<String, Object> UserAndNode = getUserAndNode(fromPath, UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			Node node = (Node) UserAndNode.get("node");
			if (node == null|| node.isFile()) {
				throw new RuntimeException("路径选择错误");
			}
			String cacheNodeJson = Redis.HGET("cache"+user.getUsername(),cacheName);
			if (cacheNodeJson == null) {
				throw new RuntimeException("缓存不存在");
			}
			Node cacheNode = Node.fromJson(cacheNodeJson);
			node.copy(cacheNode);
			if(!cacheNode.getAllSha256().isEmpty()){
				fileSha256Dao.addCounts(cacheNode.getAllSha256());
			}
			user.setFileDataJson(root.toJson());
			String data = getUser(user, UUID).getFileDataJson();
			Redis.HDEL("cache"+user.getUsername(),cacheName);
			return data;
		}catch (Exception e) {
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
	}

	/**
	 * 移动节点
	 *
	 * @param fromPath 从那个路径
	 * @param toPath   要移动到那个路径(这个只能是文件夹)
	 * @param UUID     Token
	 * @return 用户文件的信息
	 */
	@SneakyThrows
	@Override
	public String moveNode (String fromPath, String toPath, String UUID) {
		try {
			if(fromPath.equals(toPath)){
				throw new RuntimeException("移动的路径跟，目标路径不能一样.");
			}
			Map<String, Object> UserAndNode = getUserAndNode(fromPath, UUID);
			User user = (User) UserAndNode.get("user");
			Node root = (Node) UserAndNode.get("root");
			Node node = (Node) UserAndNode.get("node");
			if (node == null) {
				throw new RuntimeException("路径选择错误");
			}
			Node cacheNode =  Node.fromJson(node.toJson());
			root.removeChildByPath(fromPath);
			Node toNode = root.getChildByPath(toPath);
			if (toNode == null|| toNode.isFile()) {
				throw new RuntimeException("路径选择错误,移动到的路径不能是文件路径只能是文件夹路径");
			}
			toNode.copy(cacheNode);
			user.setFileDataJson(root.toJson());
			return getUser(user, UUID).getFileDataJson();
		} catch (Exception e){
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
}

	/**
	 * 获取缓存列表用户
	 *
	 * @param UUID 当前用户的uuid
	 * @return 缓存列表用户名称
	 */
	@SneakyThrows
	@Override
	public Set<Object> getCacheList (String UUID) {
		try {
			User user = User.fromJson(Redis.HGET(Redis.GET(UUID), "user"));
			return Redis.HKEYS("cache"+user.getUsername());
		} catch (Exception e) {
			if (e.getCause() != null) {
				String Cause = e.getCause().toString();
				if (Cause.startsWith("java.sql.SQL")) {
					throw new CustomException(500, "服务器异常.请联系管理员.");
				}
			}
			throw new CustomException(400, e.getMessage());
		}
	}

}
